//-----------------------------------------------------------------------
// <copyright file="Website.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <summary>WebSite</summary>
namespace Microsoft.Sdc.Tasks.Configuration.Web
{
    using System;
    using System.DirectoryServices;
    using System.Net;
    using System.Runtime.InteropServices;
    using System.Globalization;

    /// <summary>
    /// Summary description for WebSite.
    /// </summary>
    internal class WebSite
    {
        private const int DEFAULT_PORT = 80;
        private const IPAddress DEFAULT_IPADDRESS = null;
        private int port;
        private int identifier;
        private System.Net.IPAddress ipAddress;
        private string hostName;
        private string path;
        private string description;
        private string anonymousUserName;
        private string anonymousPassword;
        private string httpExpires;
        private VirtualDirectory root;
        private DirectoryEntry directoryEntry;
        private VirtualDirectoryCollection virtualDirectories;
        private WebDirectoryCollection webDirectories;
        private AuthFlags rootAuthFlags = 0;
        private string appPoolId;
        private string logFileDir;
        private Nullable<DirBrowseFlags> dirBrowseFlags = null;
        private string defaultDocs;
        private string machineName = "localhost";

        public int Port
        {
            get { return this.port; }
            set { this.port = value; }
        }

        public System.Net.IPAddress IPAddress
        {
            get { return this.ipAddress; }
            set { this.ipAddress = value; }
        }

        public string MachineName
        {
            get { return this.machineName; }
            set { this.machineName = value; }
        }

        public string HostName
        {
            get { return this.hostName; }
            set { this.hostName = value; }
        }

        public string Path
        {
            get { return (this.path ?? String.Empty); }
            set { this.path = value; }
        }

        public string Description
        {
            get { return (this.description ?? String.Empty); }
            set { this.description = value; }
        }

        public string AppPoolId
        {
            get { return (this.appPoolId ?? String.Empty); }
            set { this.appPoolId = value; }
        }

        public string LogFileDirectory
        {
            get { return (this.logFileDir ?? string.Empty); }
            set { this.logFileDir = value; }
        }

        public int Identifier
        {
            get { return this.identifier; }
            set { this.identifier = value; }
        }

        public string AnonymousUserName
        {
            get { return (this.anonymousUserName ?? String.Empty); }
            set { this.anonymousUserName = value; }
        }

        public string AnonymousPassword
        {
            get { return (this.anonymousPassword ?? String.Empty); }
            set { this.anonymousPassword = value; }
        }

        public string HttpExpires
        {
            get { return (this.httpExpires ?? String.Empty); }
            set { this.httpExpires = value; }
        }

        public VirtualDirectory Root
        {
            get
            {
                if (this.root == null)
                {
                    this.root = new VirtualDirectory("ROOT", this);
                }
                return this.root;
            }
        }

        public VirtualDirectoryCollection VirtualDirectories
        {
            get
            {
                if (this.virtualDirectories == null)
                {
                    this.virtualDirectories = new VirtualDirectoryCollection(this);
                }
                return this.virtualDirectories;
            }
        }

        public WebDirectoryCollection WebDirectories
        {
            get
            {
                if (this.webDirectories == null)
                {
                    this.webDirectories = new WebDirectoryCollection(this.directoryEntry.Parent);
                }
                return this.webDirectories;
            }
        }

        public AuthFlags RootAuthFlags
        {
            get { return (this.rootAuthFlags); }
            set { this.rootAuthFlags = value; }
        }

        public Nullable<DirBrowseFlags> DirBrowseFlags
        {
            get { return this.dirBrowseFlags; }
            set { this.dirBrowseFlags = value; }
        }

        public string DefaultDocs
        {
            get { return (String.IsNullOrEmpty(this.defaultDocs) ? String.Empty : this.defaultDocs); }
            set { this.defaultDocs = value; }
        }

        public WebSite(string description, string path, IPAddress ipAddress, int port, string hostName, int identifier)
        {
            this.description = description;
            this.path = path;
            this.ipAddress = ipAddress;
            this.port = port;
            this.hostName = hostName;
            this.identifier = identifier;
            this.anonymousUserName = String.Empty;
            this.anonymousPassword = String.Empty;
            this.httpExpires = String.Empty;
        }

        public WebSite(string description, string path, IPAddress ipAddress, int port, string hostName, int identifier, string anonymousUserName, string anonymousPassword)
        {
            this.description = description;
            this.path = path;
            this.ipAddress = ipAddress;
            this.port = port;
            this.hostName = hostName;
            this.identifier = identifier;
            this.anonymousUserName = anonymousUserName;
            this.anonymousPassword = anonymousPassword;
        }

        public WebSite(string description, string path) : this(description, path, DEFAULT_IPADDRESS, DEFAULT_PORT, String.Empty, 0)
        {
        }

        public WebSite(string machineName)
        {
            this.MachineName = machineName;
        }

        public WebSite()
        {
        }

        private WebSite(DirectoryEntry website)
        {
            try
            {
                this.Initialize(website);
            }
            catch (Exception e)
            {
                throw new ApplicationException("Could not load website from given DirectoryEntry", e);
            }
        }

        public static bool Exists(string machineName, int identifier)
        {
            try
            {
                if (DirectoryEntry.Exists("IIS://" + machineName + "/W3SVC/" + identifier))
                {
                    return true;
                }
            }
            catch
            {
                return false;
            }

            return false;
        }

        public static bool Exists(string machineName, string description)
        {
            int identifier = GetIdentifierFromDescription(machineName, description);
            return Exists(machineName, identifier);
        }

        public static void Delete(string description, string machineName)
        {
            int identifier = GetIdentifierFromDescription(machineName, description);
            Delete(identifier, machineName);
        }

        public static void Delete(int identifier, string machineName)
        {
            DirectoryEntry web = new DirectoryEntry("IIS://" + machineName + "/W3SVC");
            object[] args = { "IIsWebServer", identifier };
            web.Invoke("Delete", args);
            web.Dispose();
        }

        public static int GetIdentifierFromDescription(string machineName, string description)
        {
            // load the W3SVC and check all the children for a property that matches

            DirectoryEntry de = new DirectoryEntry("IIS://" + machineName + "/W3SVC");

            foreach (DirectoryEntry o in de.Children)
            {
                if (o.SchemaClassName == "IIsWebServer")
                {
                    if ((string)o.Properties["ServerComment"][0] == description)
                    {
                        return Convert.ToInt32(o.Name);
                    }
                }
            }
            return 0;
        }

        public static WebSite Load(string machineName, int identifier)
        {
            DirectoryEntry web = new DirectoryEntry("IIS://" + machineName + "/W3SVC/" + Convert.ToString(identifier));
            return new WebSite(web);
        }

        public static WebSite Load(string machineName, string description)
        {
            // load the W3SVC and check all the children for a property that matches

            int identifier = GetIdentifierFromDescription(machineName, description);

            if (identifier == 0)
            {
                throw new ApplicationException(String.Format(CultureInfo.InvariantCulture, "Unable to load website {0}", description));
            }

            DirectoryEntry o = new DirectoryEntry("IIS://" + machineName + "/W3SVC/" + Convert.ToString(identifier));
            return new WebSite(o);
        }

        /// <summary>
        /// Set the CertCheckMode property
        /// </summary>
        /// <param name="mode">Check CRL = 0, Do not check CRL > 0 </param>
        /// <returns>the original entry value</returns>
        public int CertCheckMode(int mode)
        {
            int mode2 = -1;
            if (this.directoryEntry != null)
            {
                mode2 = Convert.ToInt32(this.directoryEntry.Properties["CertCheckMode"][0]);
                this.directoryEntry.Properties["CertCheckMode"][0] = mode;
                this.directoryEntry.CommitChanges();
            }

            return mode2;
        }

        public void CreateCertificateMapping(byte[] certificate, string accountUser, string accountPassword, string accountFriendlyName)
        {
            DirectoryEntry certMapper = new DirectoryEntry(directoryEntry.Path + "/IIsCertMapper");
            certMapper.Invoke("CreateMapping", certificate, accountUser, accountPassword, accountFriendlyName, true);
        }

        public void DeleteCertificateMapping(string accountUser)
        {
            DirectoryEntry certMapper = new DirectoryEntry(directoryEntry.Path + "/IIsCertMapper");
            certMapper.Invoke("DeleteMapping", 3, accountUser);
        }

        public bool Save()
        {
            // if we don't have a directory entry
            // and if the identifier is zero
            // then try and create a new site
            // otherwise if no dir entry but identifier is set check to see if the site exists
            // if it does throw an exception (as you should have called load)
            // otherwise try to createit with the identifier provided
            if (this.directoryEntry == null)
            {
                if (this.identifier == 0)
                {
                    // we don't have an identifier so try and create it
                    this.CreateNewSite();
                }
                else
                {
                    if (Exists(this.MachineName, this.identifier))
                    {
                        throw new ApplicationException(String.Format(CultureInfo.InvariantCulture, "The WebSite '{0}' already exists. Use Website.Load to load it first", identifier));
                    }
                    else
                    {
                        // couldn't load the site with that identifier so try and create it now
                        this.CreateNewSite();
                    }
                }
            }
            else
            {
                this.UpdateWebSite();
            }

            this.directoryEntry.CommitChanges();

            // Now save the root website
            this.root.Parent = this;
            this.root.Save();

            foreach (VirtualDirectory vdir in VirtualDirectories)
            {
                vdir.Parent = this;
                vdir.Save();
            }

            return true;
        }

        public void Start()
        {
            this.directoryEntry.Invoke("Start", null);
        }

        public void Stop()
        {
            this.directoryEntry.Invoke("Stop", null);
        }

        public void Pause()
        {
            this.directoryEntry.Invoke("Pause", null);
        }

        public void Continue()
        {
            this.directoryEntry.Invoke("Continue", null);
        }

        public void AddBinding(string ipaddress, string portno)
        {
            this.AddBinding(ipaddress, portno, null, false);
        }

        public void AddBinding(string ipaddress, string portno, bool secure)
        {
            this.AddBinding(ipaddress, portno, null, secure);
        }

        public void AddBinding(string ipaddress, string portno, string hostname, bool secure)
        {
            if (ipaddress != String.Empty)
            {
                try
                {
                    IPAddress ip = System.Net.IPAddress.Parse(ipaddress);
                }
                catch (FormatException)
                {
                    throw new ApplicationException(String.Format(CultureInfo.InvariantCulture, "Specified IP '{0}' cannot be parsed, it is not a valid IP address", ipaddress));
                }
            }

            string newBinding = ipaddress + ":" + portno + ":";

            if (hostname != null)
            {
                newBinding += hostname;
            }


            if (secure)
            {
                PropertyValueCollection pvc = directoryEntry.Properties["SecureBindings"];
                if (!pvc.Contains(newBinding)) pvc.Add(newBinding);
            }
            else
            {
                PropertyValueCollection pvc = directoryEntry.Properties["ServerBindings"];
                if (!pvc.Contains(newBinding)) pvc.Add(newBinding);
            }
        }

        private void Initialize(DirectoryEntry de)
        {
            directoryEntry = de;
            this.identifier = Convert.ToInt32(de.Name);
            this.description = Convert.ToString(de.Properties["ServerComment"][0]);

            this.machineName = de.Path.Substring(6).Split('/')[0];

            this.AppPoolId = Convert.ToString(de.Properties["AppPoolId"][0]);

            string bindings = Convert.ToString(de.Properties["ServerBindings"][0]);

            string[] bindingsSplit = bindings.Split(':');
            // IP:Port:Hostname
            if (bindingsSplit[0].Length != 0)
            {
                ipAddress = System.Net.IPAddress.Parse(bindingsSplit[0]);
            }
            if (bindingsSplit[1].Length != 0)
            {
                port = Convert.ToInt32(bindingsSplit[1]);
            }

            hostName = bindingsSplit[2];
            if (root == null)
            {
                DirectoryEntry rootDE = new DirectoryEntry(de.Path + "/ROOT");
                root = new VirtualDirectory(rootDE, this);
            }
            if (virtualDirectories == null)
            {
                // Load all the webs
                DirectoryEntry rootDE = new DirectoryEntry(de.Path + "/ROOT");
                foreach (DirectoryEntry vdir in rootDE.Children)
                {
                    if (vdir.SchemaClassName == "IIsWebVirtualDir")
                    {
                        VirtualDirectories.Add(new VirtualDirectory(vdir, this));
                    }
                }
            }

            if (webDirectories == null)
            {
                DirectoryEntry rootDE = new DirectoryEntry(de.Path + "/ROOT");

                foreach (DirectoryEntry dir in rootDE.Children)
                {
                    if (dir.SchemaClassName == "IIsWebDirectory")
                    {
                        WebDirectories.Add(new WebDirectory(dir));
                    }
                }
            }
        }

        private void CreateNewSite()
        {
            if (Description.Length == 0 || Path.Length == 0)
            {
                throw new ApplicationException("You must provide a description and a path before saving a WebSite.");
            }

            using (DirectoryEntry de = new DirectoryEntry("IIS://" + this.MachineName + "/W3SVC"))
            {
                try
                {
                    DirectoryEntry de2;

                    string binding = String.Empty;
                    if (ipAddress != null)
                    {
                        binding = IPAddress.ToString();
                    }

                    binding += ":" + Port + ":" + HostName;

                    DirectoryEntry oServer = null;

                    bool bDone = false;
                    int nIndex;
                    if (identifier == 0)
                    {
                        nIndex = 1;
                    }
                    else
                    {
                        nIndex = identifier;
                    }
                    while (!bDone)
                    {
                        DirectoryEntry oSite = new DirectoryEntry("IIS://" + this.MachineName + "/W3SVC/" + nIndex);
                        try
                        {
                            object oTemp = oSite.NativeObject;
                            nIndex++;
                        }
                        catch (COMException)
                        {
                            object[] args = new object[2];
                            args[0] = "IIsWebServer";
                            args[1] = nIndex;
                            try
                            {
                                oServer = (DirectoryEntry)de.Invoke("Create", args);
                                oServer.CommitChanges();
                                DirectoryEntry oSite2 = new DirectoryEntry("IIS://" + this.MachineName + "/W3SVC/" + nIndex);
                                try
                                {
                                    object oTemp = oSite.NativeObject;
                                    bDone = true;
                                    break;
                                }
                                catch
                                {
                                    nIndex++;
                                }
                            }
                            catch
                            {
                                nIndex++;
                            }
                        }

                        if (nIndex > 1000)
                        {
                            throw new ApplicationException("Unable to create new website");
                        }
                    }
                    DirectoryEntry oRootDir = (DirectoryEntry)oServer.Invoke("Create", "IIsWebVirtualDir", "ROOT");
                    // oRootDir.CommitChanges();           

                    oRootDir.Properties["Path"][0] = Path;
                    oRootDir.Properties["AppFriendlyName"][0] = Description;
                    oRootDir.Properties["AuthFlags"][0] = rootAuthFlags;
                    if (this.anonymousUserName != null && this.anonymousUserName != String.Empty && this.anonymousUserName.Length > 0)
                    {
                        oRootDir.Properties["AnonymousUserName"][0] = this.anonymousUserName;
                    }
                    if (this.anonymousPassword != null && this.anonymousPassword != String.Empty && this.anonymousPassword.Length > 0)
                    {
                        oRootDir.Properties["AnonymousUserPass"][0] = this.anonymousPassword;
                    }

                    if (this.httpExpires != null && this.httpExpires != String.Empty && this.httpExpires.Length > 0)
                    {
                        int iHttpExpiresInHours = Convert.ToInt32(this.httpExpires);
                        if (iHttpExpiresInHours != 0)
                        {
                            int iHttpExpiresInSeconds = iHttpExpiresInHours * 3600;
                            string sHttpExpires = String.Format("D, 0x{0:X}", iHttpExpiresInSeconds);
                            oRootDir.Properties["HttpExpires"][0] = sHttpExpires;
                        }
                    }

                    if (this.DirBrowseFlags != null)
                    {
                        oRootDir.Properties["DirBrowseFlags"][0] = dirBrowseFlags;
                    }

                    if (this.DefaultDocs != String.Empty && this.DefaultDocs.Length > 0)
                    {
                        oRootDir.Properties["DefaultDoc"][0] = DefaultDocs;
                    }
                    oRootDir.CommitChanges();

                    oRootDir.Properties["AppIsolated"][0] = 2; // Medium (Pooled)
                    oRootDir.Invoke("AppCreate", new Object[] { true });

                    oRootDir.Properties["AccessRead"][0] = true;
                    oRootDir.Properties["AccessScript"][0] = true;
                    oRootDir.CommitChanges();

                    if (this.appPoolId != null && this.appPoolId.Length > 0)
                    {
                        oRootDir.Properties["AppPoolId"][0] = appPoolId;
                        oRootDir.CommitChanges();
                    }

                    de2 = new DirectoryEntry("IIS://" + this.MachineName + "/W3SVC/" + nIndex);
                    de2.Properties["ServerComment"][0] = Description;

                    if (this.LogFileDirectory.Length > 0)
                    {
                        de2.Properties["LogFileDirectory"][0] = this.LogFileDirectory;
                        // oRootDir.CommitChanges();
                    }

                    ComWrapper obj = new ComWrapper(de2.NativeObject);
                    obj.CallMethod("Put", new Object[] { "ServerBindings", binding });
                    de2.CommitChanges();
                    Initialize(de2);
                }

                catch (COMException)
                {
                }

                catch (Exception e)
                {
                    throw new ApplicationException("Unable to create new website", e);
                }
            }
        }

        private void UpdateWebSite()
        {
            try
            {
                DirectoryEntry oRootDir = new DirectoryEntry(directoryEntry.Path + "/ROOT");
                if (this.path != null && this.path != String.Empty && this.path.Length > 0)
                {
                    oRootDir.Properties["Path"][0] = Path;
                }

                if (this.rootAuthFlags != 0)
                {
                    oRootDir.Properties["AuthFlags"][0] = rootAuthFlags;
                }
                if (this.anonymousUserName != null && this.anonymousUserName != String.Empty && this.anonymousUserName.Length > 0)
                {
                    oRootDir.Properties["AnonymousUserName"][0] = this.anonymousUserName;
                }
                if (this.anonymousPassword != null && this.anonymousPassword != String.Empty && this.anonymousPassword.Length > 0)
                {
                    oRootDir.Properties["AnonymousUserPass"][0] = this.anonymousPassword;
                }

                if (string.IsNullOrEmpty(this.httpExpires) == false)
                {
                    int iHttpExpiresInHours = Convert.ToInt32(this.httpExpires);
                    if (iHttpExpiresInHours != 0)
                    {
                        int iHttpExpiresInSeconds = iHttpExpiresInHours * 3600;
                        string sHttpExpires = String.Format("D, 0x{0:X}", iHttpExpiresInSeconds);
                        oRootDir.Properties["HttpExpires"][0] = sHttpExpires;
                    }
                }

                if (this.AppPoolId != null && this.AppPoolId != String.Empty && this.AppPoolId.Length > 0)
                {
                    oRootDir.Properties["AppPoolId"][0] = this.appPoolId;
                    
                }

                if (this.DirBrowseFlags != null)
                {
                    oRootDir.Properties["DirBrowseFlags"][0] = dirBrowseFlags;
                }

                if (this.DefaultDocs != String.Empty && this.DefaultDocs.Length > 0)
                {
                    oRootDir.Properties["DefaultDoc"][0] = DefaultDocs;
                }
                oRootDir.CommitChanges();
            }

            catch (COMException)
            {
            }

            catch (Exception e)
            {
                throw new ApplicationException("Unable to modify website", e);
            }
        }

        internal DirectoryEntry GetDirectoryEntry()
        {
            return this.directoryEntry;
        }
    }
}